﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Controls;
using Ignitron.EEngine;
using Ignitron.EEngine.Maths;
using Ignitron.EEngine.Lighting;
using Ignitron.EEngine.Model;
using Board3D.Models;
using Board3D.Animation;
using GameEngine;
using System.Windows.Threading;
using System.Windows;
using Color = System.Drawing.Color;

namespace Board3D
{
    public enum eBoardView { WhiteView, BlackView, FromTop };

    public class Board3D: ContentControl
    {
        private bool _oLoaded = false;
        private string _sApplicationPath;
        private Control mHost = null;
        
        private const string _sModelPath = "AppData\\Models";
        private const string _sPicturePath = "AppData\\Pictures";

        private Engine mEngine;
       
        // world manipulation
        private EMatrix4 _worldMatrix;

        // board zooming
        private const double _maxScale = 5d;
        private const double _minScale = 2.5d;
        private double _curScale =  5d;
        private double _multScale = 0.25;
        private double _scaleStep = 0.5;
        private double _scaleVar = 0;

        // rotation - main
        private double _lastX;
        private double _lastY;
        private bool _bRotatingBoard = false;
        
        // X-axis rotation
        private const double _angleXMax = 0;
        private const double _angleXMin = -(Math.PI / 2);
        private float _angleXCur = 0;
        private const double _angleXMaxStep = 100;
        private const double _angleXStep = _angleXMin / _angleXMaxStep;

        // Y-axis rotation
        private const double _angleYMax = Math.PI;
        private const double _angleYMin = -Math.PI;
        private float _angleYCur = 0;
        private const double _angleYMaxStep = 400;
        private const double _angleYStep = _angleYMin / _angleYMaxStep;

        // blocking input
        private int _blockInputCounter = 0;
        
        private bool _lightingEnabled = false;

        // handling animation timer
        private DispatcherTimer _timerAnimation = null;

        // rotation board animations params
        RotationBoardAnimation m_RotationBoardAnimation = null;
        AnimationBlock _animBlock = null;

        private Background _backGround;
        private BoardModel _mdlBoard;

        private GameManager _mng;
        private enumBoardStyle _boardStyle;

        public event EventHandler<MoveDoneEventArgs> MoveDoneEvent;


        public Board3D()
        {          
            // set scale variable
            _scaleVar = (_curScale - _minScale) / _multScale;

            // init the engine
            InitializeEngine();

            // set startup initialization of world matrix
            RecalcWorldMatrix();
        }

        #region Public Interface Methods

        public void Redraw()
        {
            if (mEngine != null && mEngine.IsInitializationCompleted)
            {
                mEngine.InvokeRendering();
            }
        }
       
        /// <summary>
        /// Blocks user input
        /// </summary>
        public void BlockUserInput()
        {
            _blockInputCounter++;
        }

        /// <summary>
        /// Unblocks user input
        /// </summary>
        public void UnblockUserInput()
        {
            _blockInputCounter--;
        }

        /// <summary>
        /// Sets the game manager
        /// </summary>
        public void SetGameManager(GameManager manager)
        {
            _mng = manager;
            _mdlBoard.GameManager = manager;
        }

        /// <summary>
        /// Method for loading Direct X models
        /// </summary>
        public void LoadModels(string sAppPath, enumBoardStyle style)
        {
            _sApplicationPath = sAppPath;

            string sPath = Path.Combine(_sApplicationPath, _sModelPath);
            _mdlBoard = new BoardModel();
            //_mdlBoard.ShowNormals = true;
            _mdlBoard.LoadModels(mEngine, sPath, Path.Combine(_sApplicationPath, _sPicturePath), style);

            mEngine.InitializeRenderingTexture(1024, 1024);
            _mdlBoard.InitDynamicTexture(mEngine, 1024, 1024);


            _mdlBoard.MoveDoneEvent += CallMoveEventDone;

        }

        /// <summary>
        /// Sets the new 3D board style
        /// </summary>
        public void SetBoardStyle(enumBoardStyle style)
        {
            _boardStyle = style;
            if (_mdlBoard != null)
            {
                _mdlBoard.SetNewStyle(mEngine, style);
            }
        }

        /// <summary>
        /// Animates to specific board view: (white user view, black user view, view from top)
        /// </summary>
        public void SetupBoardView(eBoardView boardView)
        {
            // block user input
            BlockUserInput();

            // get final results
            double finishAngleX, finishAngleY;
            switch (boardView)
            {
                case eBoardView.WhiteView:
                    finishAngleX = -(Math.PI / 4);
                    finishAngleY = 0d;
                    break;
                case eBoardView.BlackView:
                    finishAngleX = -(Math.PI / 4);
                    finishAngleY = Math.PI;
                    break;
                case eBoardView.FromTop:
                    finishAngleX = 0d;
                    finishAngleY = 0d;
                    break;
                default:
                    throw new ApplicationException("Unknown eBoardView");
            }

            // create the animation object
            m_RotationBoardAnimation = new RotationBoardAnimation(_angleXCur, finishAngleX,
                                        _angleYCur, finishAngleY, TimeSpan.FromMilliseconds(700));
            m_RotationBoardAnimation.StartAnimation();

            // start the timer
            StartTimer(15);
        }

        #endregion

        #region Handling User Input

        void mEngine_MouseMoveEvent(object sender, EMouseEventArgs e)
        {
            // check blocking input
            if (_blockInputCounter > 0)
                return;

            double mouseX = e.Position.X;
            double mouseY = e.Position.Y;

            // if rotating board
            if (_bRotatingBoard == true)
            {
                // compute delta Y
                double deltaY = mouseY - _lastY;
                _lastY = mouseY;

                _angleXCur += (float)(deltaY * _angleXStep);
                // check bounds
                if (_angleXCur < _angleXMin)
                    _angleXCur = (float)_angleXMin;
                if (_angleXCur > _angleXMax)
                    _angleXCur = (float)_angleXMax;

                // compute delta Y
                double deltaX = mouseX - _lastX;
                _lastX = mouseX;

                _angleYCur += (float)(deltaX * _angleYStep);
                // check bounds
                if (_angleYCur < _angleYMin)
                    _angleYCur = (float)_angleYMin;
                if (_angleYCur > _angleYMax)
                    _angleYCur = (float)_angleYMax;


                // do recalculation
                _mdlBoard.SetBoardRotation((float)_angleXCur, (float)_angleYCur);
                mEngine.InvokeRendering();
            }
            else
            {
                // update the mouse hover over the board
                EVector3 collisionVector = new EVector3();
                EMatrix4 tranMatrix = _mdlBoard.ModelMatrix;
                Point point = RayTracer.MouseCoordinatesToTextureCoordinates((int)mouseX, (int)mouseY, mEngine, _mdlBoard.Mesh, tranMatrix, 1024, 1024, ref collisionVector);
                if (!(point.X < 0 || point.Y < 0))
                {
                    _mdlBoard.UpdateMouseHover(new System.Drawing.Point((int)point.X, (int)point.Y), collisionVector);
                    mEngine.InvokeRendering();
                }
            }
        }

        void mEngine_LeftButtonUpEvent(object sender, EMouseEventArgs e)
        {
            // check blocking input
            if (_blockInputCounter > 0)
                return;

            double mouseX = e.Position.X;
            double mouseY = e.Position.Y;

            EVector3 collisionVector = new EVector3();
            EMatrix4 tranMatrix = _mdlBoard.ModelMatrix;
            Point point = RayTracer.MouseCoordinatesToTextureCoordinates((int)mouseX, (int)mouseY, mEngine, _mdlBoard.Mesh, tranMatrix, 1024, 1024, ref collisionVector);
            _mdlBoard.UpdateDragAndDropFinish(new System.Drawing.Point((int)point.X, (int)point.Y));
            mEngine.InvokeRendering();
        }

        void mEngine_RightButtonUpEvent(object sender, EMouseEventArgs e)
        {
            // check blocking input
            if (_blockInputCounter > 0)
                return;

            double mouseX = e.Position.X;
            double mouseY = e.Position.Y;

            _bRotatingBoard = false;
        }

        void mEngine_LeftButtonDownEvent(object sender, EMouseEventArgs e)
        {
            // check blocking input
            if (_blockInputCounter > 0)
                return;

            double mouseX = e.Position.X;
            double mouseY = e.Position.Y;

            EVector3 collisionVector = new EVector3();
            EMatrix4 tranMatrix = _mdlBoard.ModelMatrix;
            Point point = RayTracer.MouseCoordinatesToTextureCoordinates((int)mouseX, (int)mouseY, mEngine, _mdlBoard.Mesh, tranMatrix, 1024, 1024, ref collisionVector);
            if (!(point.X < 0 || point.Y < 0))
            {
                _mdlBoard.UpdateDragAndDropStart(new System.Drawing.Point((int)point.X, (int)point.Y), collisionVector);
                mEngine.InvokeRendering();
            }
        }

        void mEngine_RightButtonDownEvent(object sender, EMouseEventArgs e)
        {
            // check blocking input
            if (_blockInputCounter > 0)
                return;
            double mouseX = e.Position.X;
            double mouseY = e.Position.Y;

            _bRotatingBoard = true;
            _lastY = mouseY;
            _lastX = mouseX;
        }

        void mEngine_MouseWheelEvent(object sender, EMouseEventArgs e)
        {
            // check blocking input
            if (_blockInputCounter > 0)
                return;

            if (e.WheelDelta < 0)
            {
                _scaleVar -= _scaleStep;
                _curScale = _scaleVar * _multScale + _minScale;
                if (_curScale < _minScale)
                {
                    _curScale = _minScale;
                    _scaleVar += _scaleStep;
                }
            }
            else
            {
                _scaleVar += _scaleStep;
                _curScale = _scaleVar * _multScale + _minScale;
                if (_curScale > _maxScale)
                {
                    _scaleVar -= _scaleStep;
                    _curScale = _maxScale;
                }
            }

            RecalcWorldMatrix();
            mEngine.InvokeRendering();
        }

        #endregion

        #region Animation Timer Methods

        public event EventHandler AnimatingMoveFinished;

        public void AnimateMove(Move animatedMove)
        {
            // block user input
            BlockUserInput();

            // build the sequence of move animation
            _animBlock = MoveAnimationBuilder.Build(animatedMove, _mdlBoard);
            _mdlBoard.StartMoveAnimation(_animBlock);
            _animBlock.StartAnimation();

            // start the timer
            StartTimer(15);           
        }

        /// <summary>
        /// Animation timer starting method
        /// </summary>
        protected void StartTimer(int uiMiliseconds)
        {
            if (_timerAnimation != null)
                return;

            _timerAnimation = new DispatcherTimer();
            _timerAnimation.Tick += TimerAnimation_Tick;
            _timerAnimation.Interval = TimeSpan.FromMilliseconds(uiMiliseconds);
            _timerAnimation.Start();
        }

        /// <summary>
        /// Animation timer handling routine
        /// </summary>
        protected void TimerAnimation_Tick(object sender, EventArgs e)
        {
            // move animation counter
            int iStopAnimationCounter = 2;

            // check rotation board animation
            if (m_RotationBoardAnimation != null)
            {
                m_RotationBoardAnimation.GetRotationParameters(out _angleXCur, out _angleYCur);
                _mdlBoard.SetBoardRotation((float)_angleXCur, (float)_angleYCur);
                mEngine.InvokeRendering();

                if (m_RotationBoardAnimation.Finished)
                {
                    iStopAnimationCounter--;
                    UnblockUserInput();
                    m_RotationBoardAnimation = null;
                }
            }
            else iStopAnimationCounter--;

            // check move animation
            if (_animBlock != null)
            {
                // update animation
                _animBlock.UpdateAnimation();
                mEngine.InvokeRendering();
                // check move animation finished
                if (_animBlock.Finished)
                {
                    // tidy up
                    iStopAnimationCounter--;
                    UnblockUserInput();
                    _mdlBoard.FinishMoveAnimation();
                    _animBlock = null;

                    // post info to the upper control, move animation finished
                    AnimatingMoveFinished(this, EventArgs.Empty);
                }

            }
            else iStopAnimationCounter--;

            // stop timer, when all animations finished
            if (iStopAnimationCounter == 0)
            {
                StopTimer();
            }
        }

        /// <summary>
        /// Stops animation timer
        /// </summary>
        protected void StopTimer()
        {
            if (_timerAnimation != null)
            {
                _timerAnimation.Stop();
                _timerAnimation = null;
            }
        }

        #endregion


        private void RecalcWorldMatrix()
        {
            _worldMatrix = EMatrix4.Identity;
            _worldMatrix *= EMatrix4.CreateTranslation(0f, 0f, (float)_curScale);
        }
      
        //void glControl_Resize(object sender, EventArgs e)
        //{
        //    if (_oLoaded == false)
        //        return;
        //    //_backGround.Resize(glControl.Width, glControl.Height);

        //    SetupViewport();
        //}

        void Engine_RenderRequestEvent(object sender, EventArgs e)
        {
            mEngine.BeginRender();

            mEngine.SetAlphaBlend(1f);
            mEngine.EnableLighting(false);
            mEngine.SetGlobalAmbient(new EVector3(1f, 1f, 1f));

            // render gameboard
            if (_mdlBoard != null)
            {
                // TO-DO: Remove background
                _mdlBoard.Background = _backGround;
                // TO-DO: Check if these params are necessary
                _mdlBoard.Render(mEngine, 1024, 1024);
            }

            mEngine.EndRender();
        }


        private void InitializeEngine()
        {
            SetupViewport();

            _backGround = new Background();
            _backGround.Compute(1024, Color.FromArgb(255, 241, 236, 30), Color.FromArgb(255, 239, 239, 239));
            mEngine.SetBackground(_backGround);


            _oLoaded = true;
        }

        void CallMoveEventDone(object sender, MoveDoneEventArgs e)
        {
            if (MoveDoneEvent != null)
                MoveDoneEvent(this, e);
        }

        private void SetupViewport()
        {
            //try
            {
                mEngine = new Engine();
                mEngine.Initialize(eEnginePlatform.DirectX11);
                AddChild(mEngine.Control);

                mEngine.SetCamera(new EVector3(0, 0, -3f), new EVector3(0, 0, 0), new EVector3(0, -1f, 0));
                mEngine.SetFOV((float)Math.PI / 4f);
                mEngine.SetNearFarLimits(0.1f, 20f);


                // bind events
                mEngine.RenderRequestEvent += Engine_RenderRequestEvent;
                mEngine.LeftButtonDownEvent += mEngine_LeftButtonDownEvent;
                mEngine.LeftButtonUpEvent += mEngine_LeftButtonUpEvent;
                mEngine.MouseMoveEvent += mEngine_MouseMoveEvent;
                mEngine.RightButtonDownEvent += mEngine_RightButtonDownEvent;
                mEngine.RightButtonUpEvent += mEngine_RightButtonUpEvent;
                mEngine.MouseWheelEvent += mEngine_MouseWheelEvent;
            }
            // handle erros due to the OpenGL initliasation failure
            //catch (Exception ex)
            {
                // TO-DO: Add not initliazed board
            }
        }
    }
}
